//
//  BERxCacheExtension.swift
//  BasicExtensions
//
//  Created by 范晓鑫 on 2022/12/8.
//

import RxSwift

/// 缓存用法，只取决于第一个元素，后续的元素都会自动更新缓存
public enum BERxCacheUsage{
    /// 有缓存则使用缓存，不再执行原操作也不会有后续元素，没有则执行原操作
    case notUpdate
    /// 有缓存则使用缓存，并执行原操作更新缓存，更新后再次发送
    case update
    /// 有缓存也不使用缓存，执行原操作更新缓存，更新后发送
    case updateOnly
    /// 有缓存则使用缓存，并执行原操作更新缓存，但不再发送
    case updateSilently
}

extension ObservableType {
    /// 使用缓存
    /// - Parameters:
    ///   - key: 缓存的键
    ///   - dueTime: 缓存时长, never表示永不过期
    ///   - usage: 缓存用法
    public func cache(key: String, dueTime: RxTimeInterval = .never, usage: BERxCacheUsage = .notUpdate) -> Observable<Element> {
        return BERxCache(source: self, key: key, dueTime: dueTime, usage: usage).asObservable()
    }
}
/// 可缓存协议，继承该协议的对象可以不用输入key，只可用于全局对象，因为在取缓存时对象还不会发送，所以不能针对对象来做缓存，只能针对类，针对对象的还是要手动输入key，如请求链接，在请求之前则可以根据链接来生成key
public protocol BERxCacheable: BESerializable {
    /// 缓存用的键
    static var cacheKey: String { get }
    /// 缓存用法
    static var cacheTime: RxTimeInterval { get }
    /// 缓存用法
    static var cacheUsage: BERxCacheUsage { get }
}
extension BERxCacheable {
    /// 缓存用法
    public static var cacheTime: RxTimeInterval { return .never }
    /// 缓存用法
    public static var cacheUsage: BERxCacheUsage { return .notUpdate }
}

extension ObservableType where Element: BERxCacheable {
    /// 使用缓存
    /// - Parameters:
    ///   - key: 缓存的键
    ///   - dueTime: 缓存时长, never表示永不过期，nil则使用BERxCacheable声明的
    ///   - usage: 缓存用法，nil则使用BERxCacheable声明的
    public func cache(dueTime: RxTimeInterval? = nil, usage: BERxCacheUsage? = nil) -> Observable<Element> {
        return BERxCache(source: self, key: Element.cacheKey, dueTime: dueTime ??  Element.cacheTime, usage: usage ??  Element.cacheUsage).asObservable()
    }
}

final private class BERxCache<T: ObservableType>: ObservableType, BEElementSerializable {
    typealias Element = T.Element
    /// 原始的源
    fileprivate let source: T
    /// 缓存的键，同一个键代表同一个缓存
    fileprivate let key: String
    /// 缓存文件的有效时长
    fileprivate let dueTime: RxTimeInterval
    /// 缓存用法
    fileprivate let usage: BERxCacheUsage
    /// 缓存文件路径
    private var fileURL: URL? {
        let directory = "\(Self.self)"
        return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent(directory.data(using: .utf8)?.base64EncodedString() ?? directory).appendingPathComponent(key.data(using: .utf8)?.base64EncodedString() ?? key)
    }
    /// 最新缓存的元素
    private var _lastCachedInfo: CacheInfo?
    /// 最新缓存的元素，且判断有效期
    private var lastCachedElement: Element? {
        if let _lastCachedInfo {
            let element = _lastCachedInfo.element
            let seconds = -_lastCachedInfo.time.timeIntervalSinceNow
            let dueSeconds = self.dueTime.asTimeInterval()
            if seconds < dueSeconds {
                return element
            } else {
                return nil
            }
        }
        return nil
    }
    /// 处理过的源
    private lazy var observable: Observable<Element> = {
        // 因为自身是class会被释放，所以在订阅的时候需要手动retain，在dispose的时候再release
        Unsafe(self).retain()
        return source.do(onNext: { [unowned self] element in
            self._lastCachedInfo = self.cache(element: element)
        }, onDispose: { [unowned self] in
            Unsafe(self).release()
        })
    }()
    
    init(source: T, key: String, dueTime: RxTimeInterval, usage: BERxCacheUsage) where T.Element == Element {
        self.source = source
        self.key = key
        self.dueTime = dueTime
        self.usage = usage
        
        self._lastCachedInfo = self.cachedInfo()
    }
    func subscribe<Observer>(_ observer: Observer) -> Disposable where Observer : ObserverType, Element == Observer.Element {
        switch self.usage {
        case .notUpdate:
            if let lastCachedElement {
                observer.onNext(lastCachedElement)
                observer.onCompleted()
                return Disposables.create { }
            } else {
                return self.observable.subscribe(observer)
            }
        case .update:
            if let lastCachedElement {
                observer.onNext(lastCachedElement)
            }
            return self.observable.subscribe(observer)
        case .updateOnly:
            return self.observable.subscribe(observer)
        case .updateSilently:
            if let lastCachedElement {
                observer.onNext(lastCachedElement)
                return self.observable.skip(1).subscribe(observer)
            } else {
                return self.observable.subscribe(observer)
            }
        }
    }
}
extension BERxCache {
    private struct CacheInfo {
        /// 缓存元素
        let element: Element
        /// 缓存时间
        let time: Date
    }
    /// 获取已缓存的元素
    private func cachedInfo() -> CacheInfo? {
        if let fileURL,
           let data = FileManager.default.contents(atPath: fileURL.path),
           let element = try? self.element(for: data),
           let attributes =  try? FileManager.default.attributesOfItem(atPath: fileURL.path),
           let time = (attributes[.modificationDate] ?? attributes[.creationDate]) as? Date {
            return .init(element: element, time: time)
        } else {
            return nil
        }
    }
    /// 缓存元素
    private func cache(element: Element) -> CacheInfo? {
        if let data = try? self.data(for: element),
           let fileURL {
            try? data.wirteAndCreateDirectories(to: fileURL)
            return .init(element: element, time: Date())
        } else {
            return nil
        }
    }
}
